package models

import (
	"log"
	"sync"
	"time"
)

var (
	once     sync.Once  // 单例模式
	instance *snowflake // 返回的对象
)

const (
	epotch            = int64(1577808000000)                           // 设置起始时间（时间戳/毫秒）：2020-01-01 00：00：00 有效期 69 年
	timestampBits     = uint(41)                                       // 时间戳占用位数
	datacenterIdBits  = uint(3)                                        // 数据中心 id 所占位数
	workerIdBits      = uint(7)                                        // 机器 id 所占的位数
	sequenceBits      = uint(12)                                       // 序列所占的位数
	timestampMax      = int64(-1 ^ (-1 << timestampBits))              // 时间戳最大值
	datacenterIdMax   = int64(-1 ^ (-1 << datacenterIdBits))           // 支持的最大数据中心 id 数量
	workerIdMax       = int64(-1 ^ (-1 << workerIdBits))               // 支持的机器 id 数量
	sequenceMax       = int64(-1 ^ (-1 << sequenceBits))               // 支持的最大序列 id 数量
	workerIdShift     = sequenceBits                                   // 机器 id 左移位数
	datacenterIdShift = sequenceBits + workerIdBits                    // 数据中心 id 左移位数
	timestampShift    = sequenceBits + workerIdBits + datacenterIdBits // 时间戳左移位数

)

type snowflake struct {
	sync.Mutex         // 锁
	timestamp    int64 // 时间戳，ms
	workerId     int64 // 工作节点
	datacenterId int64 // 数据中心机房 id
	sequence     int64 // 序列号
}

// NextVal： 生成唯一 ID，使用单例模式保证全局一致性
func (s *snowflake) NextVal() int64 {
	s.Lock()
	now := time.Now().UnixNano() / 1000000 // 转毫秒
	if s.timestamp == now {
		// 当同一时间戳 （精度: 毫秒）下次多次生成 id 会增加序列号
		s.sequence = (s.sequence + 1) & sequenceMax
		if s.sequence == 0 {
			// 如果当前序列超出 12bit 长度，则需要等待下一毫秒
			// 下一毫秒将使用 sequence:0
			for now <= s.timestamp {
				now = time.Now().UnixNano() / 1000000
			}
		}
	} else {
		// 不同时间戳（精度：毫秒）下直接使用序列号：0
		s.sequence = 0
	}
	t := now - epotch
	if t > timestampMax {
		s.Unlock()
		log.Printf("epoch must be between 0 and %d\n", timestampMax-1)
		return 0
	}
	s.timestamp = now
	r := int64((t)<<int64(timestampShift) | (s.datacenterId << int64(datacenterIdShift)) | s.workerId<<int64(workerIdShift) | (s.sequence))
	s.Unlock()
	return r
}

func NewSnowflake() *snowflake {
	once.Do(func() {
		instance = new(snowflake)
	})

	return instance
}
